/*
 * @(#)SimplifiedPathIterator.java
 *
 * $Date: 2011-05-02 16:01:45 -0500 (Mon, 02 May 2011) $
 *
 * Copyright (c) 2011 by Jeremy Wood.
 * All rights reserved.
 *
 * The copyright of this software is owned by Jeremy Wood.
 * You may not use, copy or modify this software, except in
 * accordance with the license agreement you entered into with
 * Jeremy Wood. For details see accompanying license terms.
 *
 * This software is probably, but not necessarily, discussed here:
 * http://javagraphics.java.net/
 *
 * That site should also contain the most recent official version
 * of this software.  (See the SVN repository for more details.)
 */
package com.bric.geom;

import java.awt.geom.PathIterator;

/** This filters a <code>PathIterator</code> and makes sure
 * that each curve is of the smallest possible degree.
 * <P>In addition to being more efficient, this can avoid
 * divide-by-zero errors for some operations.
 *
 */
public class SimplifiedPathIterator implements PathIterator {
    /** This is the tolerance a term can be to be considered "zero".
     * This value is .0001.
     */
    final static private double TOL = .0001;
    PathIterator i;
    double lastX, lastY;

    /** Creates a new SimplifiedPathIterator that filters
     * the argument.
     *
     * @param i
     */
    public SimplifiedPathIterator(PathIterator i) {
        this.i = i;
    }

    /** Returns true if all 3 points are on the same line.
     * This checks to see if:
     * <BR><code>x1*(y2-y3)+x2*(y3-y1)+x3*(y1-y2)<TOL*TOL</code>
     *
     * @param x1 the x-coordinate of point #1
     * @param y1 the y-coordinate of point #1
     * @param x2 the x-coordinate of point #2
     * @param y2 the y-coordinate of point #2
     * @param x3 the x-coordinate of point #3
     * @param y3 the y-coordinate of point #3
     * @return true if all 3 points are on the same line.
     */
    public static boolean collinear(double x1,double y1,double x2,double y2,double x3,double y3) {
        double determinant =
            x1*(y2-y3)+x2*(y3-y1)+x3*(y1-y2);
        return Math.abs(determinant)<TOL*TOL;
    }

    private static double[] doubleArray = new double[6];
    /** This possibly reduces the degree of a segment, if possible.
     *
     * @param type the current expected type of the segment data
     * @param lastX the previous X value from which this segment begins
     * @param lastY the previous Y value from which this segment begins
     * @param data the data of the current segment.
     * @return the new segment type, or the original <code>type</code>
     * argument if nothing was modified.
     */
    public static int simplify(int type,float lastX,float lastY,float[] data) {
        synchronized(doubleArray) {
            for(int a = 0; a<data.length; a++) {
                doubleArray[a] = data[a];
            }
            int returnValue = simplify(type, lastX, lastY, doubleArray);
            for(int a = 0; a<data.length; a++) {
                data[a] = (float)doubleArray[a];
            }
            return returnValue;
        }
    }

    /** This possibly reduces the degree of a segment, if possible.
     *
     * @param type the current expected type of the segment data
     * @param lastX the previous X value from which this segment begins
     * @param lastY the previous Y value from which this segment begins
     * @param data the data of the current segment.
     * @return the new segment type, or the original <code>type</code>
     * argument if nothing was modified.
     */
    public static int simplify(int type,double lastX,double lastY,double[] data) {
        if(type==SEG_CUBICTO) {
            if(collinear(lastX,lastY,data[4],data[5],data[0],data[1])
                    && collinear(lastX,lastY,data[4],data[5],data[2],data[3])) {
                data[0] = data[4];
                data[1] = data[5];
                return SEG_LINETO;
            }


            double ax = -lastX + 3 * data[0] - 3 * data[2] + data[4];
            double ay = -lastY + 3 * data[1] - 3 * data[3] + data[5];

            if(Math.abs(ax)<.000001 && Math.abs(ay)<.000001) {
                double bx = 3 * lastX - 6 * data[0] + 3 * data[2];
                double cx = -3 * lastX + 3 * data[0];
                //double dx = lastX;
                double by = 3 * lastY - 6 * data[1] + 3 * data[3];
                double cy = -3 * lastY + 3 * data[1];
                //double dy = lastY;

                data[1] = (cy+2*lastY)/2.0;
                data[3] = by-lastY+2*data[1];

                data[0] = (cx+2*lastX)/2.0;
                data[2] = bx-lastX+2*data[0];

                return simplify(PathIterator.SEG_QUADTO, lastX, lastY, data);
            }
        } else if(type==SEG_QUADTO) {
            if(collinear(lastX,lastY,data[2],data[3],data[0],data[1])) {
                data[0] = data[2];
                data[1] = data[3];
                return SEG_LINETO;
            }

            double ax = lastX - 2 * data[0] + data[2];
            double ay = lastY - 2 * data[1] + data[3];
            if(Math.abs(ax)<.000001 && Math.abs(ay)<.000001) {
                double bx = -2 * lastX + 2 * data[0];
                //double cx = lastX;
                double by = -2 * lastY + 2 * data[1];
                //double cy = lastY;

                data[0] = (bx+2*lastX)/2.0;
                data[1] = (by+2*lastY)/2.0;
                return PathIterator.SEG_LINETO;
            }
        }

        return type;
    }

    public int currentSegment(double[] f) {
        int type = i.currentSegment(f);
        type = simplify(type,lastX,lastY,f);
        if(type==PathIterator.SEG_LINETO || type==PathIterator.SEG_MOVETO) {
            lastX = f[0];
            lastY = f[1];
        } else if(type==PathIterator.SEG_QUADTO) {
            lastX = f[2];
            lastY = f[3];
        } else if(type==PathIterator.SEG_CUBICTO) {
            lastX = f[4];
            lastY = f[5];
        }
        return type;
    }

    private double[] d;
    public int currentSegment(float[] f) {
        if(d==null) {
            d = new double[6];
        }
        int k = currentSegment(d);
        f[0] = (float)d[0];
        f[1] = (float)d[1];
        f[2] = (float)d[2];
        f[3] = (float)d[3];
        f[4] = (float)d[4];
        f[5] = (float)d[5];
        return k;
    }

    public int getWindingRule() {
        return i.getWindingRule();
    }

    public boolean isDone() {
        return i.isDone();
    }

    public void next() {
        i.next();
    }
}
